package parsetree;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

import javax.swing.text.BadLocationException;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

import metagrammar.Operator;
import metagrammar.Type;
import metagrammar.Typed;

import com.google.common.primitives.Ints;

/**
 * This is an generalization of {@linkplain OperatorSpec} which allows for any number of string-literals to
 * be a child of the resulting Element, ie:
 * a fully parsed Element built from this will be of a form generatable from:
 * <blockquote><code>
 * S  => A|B0</br>
 * A  => x0</br>
 * Bi => Ei xi Bi+1|Ei xi|Ei|xi
 * </code>
 * </blockquote>
 * where <code>xi</code> is some representative of a terminal string
 * and <code>Ei</code> is some {@linkplain Typed} Element such that
 * <code>
 * Ei.{@linkplain Typed#getType() getType()} ==
 * {@linkplain #getCorrectedInputTuple() getCorrectedInputTuple()}[i].getType().
 * </code>
 * 
 * @author Joshua Lockerman
 *
 */
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class MultipartOperatorSpec extends OperatorSpec {
    
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class LiteralPositionPair {
        
        private String literal;
        private final int position;
        
        public LiteralPositionPair(String literal, int position)
        {
            this.literal = literal;
            this.position = position;
        }
        
        private void setLiteral(String literal)
        {
            this.literal = literal;
        }
        
        private String getLiteral()
        {
            return literal;
        }
        
        private int getPosition()
        {
            return position;
        }
        
        /* (non-Javadoc)
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((literal == null) ? 0 : literal.hashCode());
            result = prime * result + position;
            return result;
        }
        
        /* (non-Javadoc)
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj)
        {
            if(this == obj) {
                return true;
            }
            if(obj == null) {
                return false;
            }
            if( !(obj instanceof LiteralPositionPair)) {
                return false;
            }
            LiteralPositionPair other = (LiteralPositionPair) obj;
            if(literal == null) {
                if(other.literal != null) {
                    return false;
                }
            }
            else if( !literal.equals(other.literal)) {
                return false;
            }
            if(position != other.position) {
                return false;
            }
            return true;
        }
    }
    
    public static class Generator {
        
        private final Operator operator;
        private final HashMap<Integer, LiteralPositionPair> usedPositions;
        boolean active;
        
        public Generator(Operator operator)
        {
            this.operator = operator;
            usedPositions = new HashMap<Integer, LiteralPositionPair>();
            active = true;
        }
        
        public Generator add(String literal, int position)
        {
            if(active) {
                LiteralPositionPair pos = usedPositions.get(position);
                if(pos == null) {
                    pos = new LiteralPositionPair(literal, position);
                    usedPositions.put(position, pos);
                }
                else {
                    pos.setLiteral(literal);
                }
            }
            return this;
        }
        
        public MultipartOperatorSpec finish()
        {
            active = false;
            LiteralPositionPair[] positions = new LiteralPositionPair[usedPositions.size()];
            usedPositions.values().toArray(positions);
            Arrays.sort(positions, new Comparator<LiteralPositionPair>() {
                @Override
                public int compare(LiteralPositionPair o1, LiteralPositionPair o2)
                {
                    return Ints.compare(o1.getPosition(), o2.getPosition());
                }
            });
            return new MultipartOperatorSpec(operator, positions);
        }
    }
    
    public static Generator getGenerator(Operator operator)
    {
        return new Generator(operator);
    }
    
    private class OperatorSpecWithPrecedence extends MultipartOperatorSpec {
        
        private final float precedence;
        
        public OperatorSpecWithPrecedence(float precedence)
        {
            this.precedence = precedence;
        }
        
        @Override
        public OperatorElement generateElement(BranchElement parent, int operatorStart,
                int operatorEnd) throws BadLocationException
                {
            return MultipartOperatorSpec.this.generateElement(parent, operatorStart, operatorEnd);
                }
        
        @Override
        public List<Type> getCorrectedInputTuple()
        {
            return MultipartOperatorSpec.this.getCorrectedInputTuple();
        }
        
        @Override
        public int[] getCorrectedInputTupleOrder()
        {
            return MultipartOperatorSpec.this.getCorrectedInputTupleOrder();
        }
        
        @Override
        public String getLiteral()
        {
            return MultipartOperatorSpec.this.getLiteral();
        }
        
        @Override
        public int getLiteralBeforePosition()
        {
            return MultipartOperatorSpec.this.getLiteralBeforePosition();
        }
        
        @Override
        public Operator getOperator()
        {
            return MultipartOperatorSpec.this.getOperator();
        }
        
        @Override
        public MultipartOperatorSpec withPrecedence(float precedence)
        {
            return MultipartOperatorSpec.this.withPrecedence(precedence);
        }
        
        @Override
        public float getPrecedence()
        {
            return precedence;
        }
    }
    
    private final LiteralPositionPair[] positions;
    
    private MultipartOperatorSpec()
    {
        positions = null;
    }
    
    public MultipartOperatorSpec(Operator operator, int[] correctedOrder,
            LiteralPositionPair[] positions)
    {
        super(operator, correctedOrder, positions[0].getLiteral(), positions[0].getPosition());
        this.positions = positions;
    }
    
    private MultipartOperatorSpec(Operator operator, LiteralPositionPair[] positions)
    {
        this(operator, null, positions);
    }
    
    @Override
    public MultipartOperatorSpec withPrecedence(float precedence)
    {
        return new OperatorSpecWithPrecedence(precedence);
    }
    
    @Override
    public OperatorElement generateElement(BranchElement parent, int operatorStart, int operatorEnd)
            throws BadLocationException
            {
        // TODO Auto-generated method stub
        return super.generateElement(parent, operatorStart, operatorEnd);
            }
    
    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode()
    {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + Arrays.hashCode(positions);
        return result;
    }
    
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj)
    {
        if(this == obj) {
            return true;
        }
        if( !super.equals(obj)) {
            return false;
        }
        if( !(obj instanceof MultipartOperatorSpec)) {
            return false;
        }
        MultipartOperatorSpec other = (MultipartOperatorSpec) obj;
        if( !Arrays.equals(positions, other.positions)) {
            return false;
        }
        return true;
    }


}
